<?php
/**
 * Parcl
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://parcl.com/license
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@parcl.com so we can send you a copy immediately.
 *
 * @category parcl
 * @package parcl-core
 * @copyright Copyright (c) 2006-2011 Mikael Knutsson (http://mikn.me)
 * @license http://parcl.com/license New BSD License
 * @author Mikael Knutsson
 */

namespace Parcl\Core {

	if(PARCL_EXEC !== 1) {
		die('<h1>Incorrect Access</h1>You cannot access this file directly.<br/>' .
			'If you got this error message by clicking a link on the site, ' .
			'please notify the webmaster as soon as possible.');
	}

	/**
	 * A singleton class that scans for modules and holds the collection of them.
	 * The registry class puts it into the registry though.
	 *
	 * @uses MODULE_DIR Depends on this variable being correctly defined.
	 * @todo Currently assumes that at least one module exists.
	 */

	class ScanModules implements ISingleton {
		/**
		 * @var array Contains the modules found. Since it's a singleton, this
		 * persists and only needs to be filled once per execution.
		 */
		private $modules = array();
		/**
		 * @var array Contains all custom module queries. This means, all "which
		 * modules have this file or folder in it?" queries.
		 */
		private $modulesContaining = array();
		/**
		 * @var boolean Switch to make sure scanning for modules only happens once.
		 */
		private $scanned = false;
		/**
		 * @var boolean Switch to make sure that the modules only gets checked once.
		 */
		private $checked = false;
		/**
		 * @var array Contains the file types a module needs to have.
		 */
		private $fileTypes = array('.php', '.xml');
		/**
		 * @var ScanModules Contains the instance of the singleton.
		 */
		private static $instance = null;

		private function __construct() {}

		/**
		 * Fetches the instance of the class.
		 * @return ScanModules An instance of the class.
		 */
		public static function GetInstance() {
			if(is_null(self::$instance)) {
				self::$instance = new ScanModules();
			}
			return self::$instance;
		}

		private function FindModules() {
			$modules = scandir(MODULE_DIR);
			foreach($modules as $k => $module) {
				if(substr($module, 0, 1) != '.' && is_dir(MODULE_DIR . $module)) {
					$this->modules[] = $module;
				}
			}
			$this->scanned = true;
			return $this->modules;
		}
		private function checkModule($file, $modDir) {
			if(!in_array($file, $modDir)) {
				throw new GeneralException('Malformed module in module directory! ' .
									   'Directory: ' . MODULE_DIR . print_r($modDir, true) .
									   ' Does not contain a ' . $file . ' file with the same name.',
									   GeneralException::MODULES);
			}
		}

		private function scanFor($itemUnparsed) {
			// Hidden files are hidden for a reason, so if it contains a dot, we assume it's a file.
			$isType = 'is_' . (mb_strpos($itemUnparsed, '.', 1) ? 'file' : 'dir');


			if(count($this->modules)  == 0) {
				$this->FindModules();
			}
			foreach($this->modules as $module) {
				// Parse the item string.
				$item = strtr($itemUnparsed, array('%module', $module));
				// Scans the module directory.
				$moduleDir = scandir(MODULE_DIR . $module);
				// Check modules to make sure they conform to structure, but only check once.
				if(!$this->checked) {
					foreach($this->fileTypes as $fileType) {
						$this->checkModule($module . $fileType, $moduleDir);
					}
				}
				if(in_array($item, $moduleDir) && $isType(MODULE_DIR . $module . '/' . $item)) {
					$this->modulesContaining[$itemUnparsed][] = $module;
				}
			}
			$this->checked = true;
			return $this->modulesContaining[$item];
		}

		/**
		 * Fetches all modules present.
		 * @return array All modules found in the MODULE_DIR.
		 */
		public function FindModules() {
			if(!$this->scanned) {
				return $this->FindModules();
			}
			else {
				return $this->modules;
			}
		}
		/**
		 * Scans for specific items in each module directory. Supports %module to
		 * scan for files or folders containing *modulename*blah.file-ending for
		 * instance.
		 * @param string $item The item to scan for. Can contain %module to be
		 * replaced by the module currently being scanned.
		 * @return array Contains all modules which has an item matching the
		 * specific pattern.
		 */
		public function ScanFor($item) {
			if(!isset($this->modulesContaining[$item])) {
				return $this->scanFor($item);
			}
			else {
				return $this->modulesContaining[$item];
			}
		}
	}
}